Genesis 3D |
||
per collaborazioni, commenti, critiche, e altro contattateci alla e-mail: clubinfo@libero.it risponderemo al più presto! |
Tredicesima
lezione
Per commentare, dare suggerimenti
o consigli (molto apprezzati) e per segnalare eventuali errori o
disattenzioni (sempre possibili), puoi inviare una email all'autore (clicca
sul nome sopra). Grazie per la collaborazione!
In questo tutorial vedremo:
1) Mappe e modelli procedurali
2) Modelli basati sulla grammatica
3) Sistemi particellari
4) Sistemi basati sulla fisica
5) Corona e Dynamic Light
Molti fenomeni naturali non sono rappresentati in modo efficiente
mediante le tecniche di modellazione standard. La nebbia per esempio è composta
da una moltitudine di minuscole particelle d'acqua; realizzare l'effetto nebbia
tenendo conto di tutte queste molecole, ogniuna delle quali deve essere
posizionata è proibitivo. Inoltre nessuno ci assicura che al momento del
rendering l'effetto ottenuto è esattamente quello desiderato.
Per risolvere il problema dobbiamo concentrarci su quella che è la
nostra percezione della nebbia, ovvero come la nebbia altera il nostro sistema
di percezione della luce nell'ambiente.
Il modello che si ottiene è quindi più efficiente, e adatto anche ad
una renderizzazione in tempo reale (si noti come molti giochi usino l'effetto
nebbia in modo intensivo).
I fenomeni naturali non sono gli unici che richiedono tecniche
avanzate: si pensi al ponte di Brooklyn nei dettagli. Esso è realizzato da
bulloni e dadi non sempre posizionati nelle stesse posizioni. Il problema di
modellare un oggetto così complesso può essere superato andando oltre la
modellazione geometrica.
I modelli procedurali rappresentano degli oggetti in grado di
interagire con eventi esterni e di modificare la propria struttura (sia essa la
geometria, oppure altri parametri come l'origine del sistema di coordinate).
Ad esempio una sfera che genera una rappresentazione poligonale
differente in base ad un parametro di ingresso è un modello procedurale.
Il grande vantaggio dei modelli procedurali è che permettono di
risparmiare spazio. Infatti è possibile sostituire a "collezioni di
oggetti" la loro descrizione parametrica. Si pensi alla descrizione
procedurale di un ponte. Esso è composto da sottomodelli come strade, piloni e
parapetti e può essere specificato fornendo la descrizione di questi elementi.
Ogni "pezzo" è definito da una serie di parametri e da una procedura
che specifica come posizionarlo.
Un importante aspetto dei modelli procedurali è la loro abilità di
interagire con l'ambiente. Tale capacità viene usata per controllare la forma
dell'oggetto definita nella procedura.
Un particolare uso dei modelli procedurali sono le mappe procedurali.
Si tratta di una texture che in base ad alcuni parametri può essere variata (si
può modificare il colore, il motivo e l'aspetto generale). 3D studio MAX offre
tale caratteristica, suddividendola in mappe 2D e mappe 3D. La mappa 2D non è
altro che una texture procedurale, che può essere applicata poi ad un oggetto.
MAX offre già alcune mappe pronte, quali scacchiera, gradiente e vortice. Le
mappe 3D invece rappresentano un modo per definire le caratteristiche del
materiale dell'oggetto senza passare attraverso la bitmap (come nel caso 2D).
Il motivo generato è direttamente nello spazio 3D. Alcune mappe 3D sono legno e
marmo.
Esempi di mappe 2D (gradiente e vortice)
Esempi di mappe 3D
Genesis supporta pure le mappe procedurali (ma solo quelle che MAX
definisce 2D, ovvero bitmap). Tuttavia si tratta di una caratteristica estremamente
poco documentata e che risulta difficile da sperimentare. Infatti non è
possibile al momento esportare le mappe di MAX per l'uso in genesis. La più
evidente mappa procedurale (gestita automaticamente nell'engine di genesis) è
lo specchio; una texture impostata come specchio cambia a seconda dell'ambiente
circostante.
Per definire delle mappe procedurali in genesis si deve creare un file
.prc che deve essere associato al file .bsp della mappa.
Tale file deve contenere una descrizione della mappa:
wbm: FireProc1 Smoke
(Smoke_PalFire, 128, 6.0, 64.0, 25.0, 4.0, 5.0, 1.0);
dove FireProc1 è la texture (che deve esistere nel file .txl) alla
quale si vuole associare l'effetto; smoke è il nome della procedura che
contiene l'effetto (nel caso specifico il fumo); i restanti sono i parametri
standard della procedura. Naturalmente in fase di esecuzione è possibile
variare tali parametri e quindi l'effetto complessivo del fumo.
Si tratta di veri e propri linguaggi usati per fornire la descrizione
di oggetti la cui struttura è ordinata come quella di una pianta. Tali
linguaggi sono descritti da una grammatica che consiste della collezione di
produzioni. Ad esempio:
1. A -> AA
2. B -> A[B]AA[B]
Partendo dall'assioma A si generano A, AA, AAAA e così via. partendo
dall'assioma B si generano B, A[B]AA[B], AA[A[B]AA[B]]AA[A[B]AA[B]], e così
via.
A tale struttura possiamo associare un grafo o un albero arrivando a
generare così in maniera automatica strutture grandi quanto si vuole. Il grafo
può a sua volta essere rappresentato come una pianta, dove le A rappresentano
le parti di tronco e le B le foglie. Con le parentesi quindi si specificano i
rami della pianta.
Acqua, fuoco, neve, fumo, pioggia sono tutti esempi di fenomeni che
vengono resi mediante sistemi particellari. Un sistema particellare è definito
come una collezione di oggetti elementari (particelle) che evolve nel tempo.
L'evoluzione è determinata applicando certe regole probabilistiche. Le
particelle posseggono una propria evoluzione che parte dalla nascita, dalla
crescita e dallo spostamento e finisce con la morte. L'essenza del sistema
particellare è che la posizione e l'evoluzione delle singole particelle è
controllata in modo automatico e ogni particella influisce direttamente
l'immagine.
Il rendering dei sistemi particellari è un argomento notevole.
Effettuare il ray tracing di un sistema di particelle, ognuna con il proprio
bounding box diventa impossibile quando le particelle sono numerose. Una soluzione
fornita da Reeves è quella di considerare ogni particella come una singola
sorgente di luce e di calcolare l'apporto di ogni singola sorgente
sull'immagine fiale. Il valore di ogni pixel viene determinato dall'accumulo
delle componenti di ogni particella.
3D studio MAX ha un buon supporto dei sistemi particellari. In MAX le
singole particelle rispondono a forze comuni e sono intrinsecamente animati. Le
particelle vengono emesse da una sorgente e si spostano lungo un percorso
regolare. E' possibile controllare in modo abbastanza sofisticato il
comportamento delle particelle mediante l'uso di vari modificatori space warp.
Inoltre in fase di rendering è possibile associare ad ogni particella
delle istanze di oggetti. In questo modo ad esempio è possibile modellare un
branco di pesci semplicemente associando ad ogni particella la geometria di un
pesce.
Esempio di neve:
Il comportamento e la forma di molti oggetti è determinata dalle leggi
fisiche che regolano il nostro mondo e dalle caratteristiche intrinseche
dell'oggetto. Esempi di leggi fisica sono la gravità, la conservazione della
quantità di moto, la trasformazione dell'energia cinetica in potenziale, mentre
esempi di caratteristiche degli oggetti sono il peso, il coefficiente elastico,
ecc...
In questo tipo di modellazione, non è l'utente a determinare le curve e
i percorsi dell'animazione. L'utente stabilisce quali sono gli oggetti in gioco
e le loro proprietà fisiche e poi è l'applicazione che mediante l'uso di leggi
matematiche calcola il comportamento degli oggetti, tenendo conto anche delle
interazioni tra di essi.
3D studio MAX possiede questi sistemi, che chiama "simulazioni
dinamiche". Si tratta di un metodo di generazione automatica delle curve
di animazione che poi possono modificate. Tali simulazioni hanno una grande
consumo di memoria e tempo macchina, che naturalmente aumentano ancora con
l'aumentare del numero di oggetti e con la precisione richiesta. E' inoltre
richiesta, da parte dell'utente, una buona conoscenza della fisica.
Genesis offre la possibilità di creare dei sistemi fisici, ma anche
questa è una caratteristica poco documentata. Per creare un sistema basato
sulla fisica si deve aggiungere un oggetto di tipo PhysicalSytem al world.
Quindi si devono creare degli oggetti "fisici" la cui struttura è
data di seguito:
gePhysicsObject_Create(const
geVec3d *StartLocation,
float
mass,
geBoolean
IsAffectedByGravity,
geBoolean
RespondsToForces,
float
linearDamping,
float
angularDamping,
const
geVec3d * Mins,
const geVec3d * Maxs,
float
physicsScale);
Come si vede si può assegnare una massa, se è affetto da gravità e
dalle forze fisiche che è possibile definire, se ha uno smorzamento nel
movimento e nella rotazione e il bounding box.
Il parametro physicsScale serve per scalare i valori di tutte le forze
che agiscono sull'oggetto.
Un PhysicsObject è soggetto ai colcoli generati da PhysicsSystem.
L'oggetto PhysicsJoint serve a connettere tra di loro due PhysicsObjects per
generare effetti come l'intreccio di una catena.
gePhysicsJoint_Create(gePhysicsJoint_Kind
Kind,
const geVec3d
*Location,
float assemblyRate,
gePhysicsObject
*PS1,
gePhysicsObject
*PS2,
float
physicsScale);
Per quanto riguarda il tipo si ha:
typedef enum { JT_WORLD = 0,
JT_SPHERICAL, JT_PTTOPATH, JT_PTTOSURFACE} gePhysicsJoint_Kind;
ma del significato di questo e degli altri parametri non viene data
alcuna spiegazione.
Poi mediante funzioni tipo
gePhysicsSystem_Iterate() viene aggiornato il sistema fisico (ovvero
vengono eseguiti i calcoli).
gePhysicsObject_GetXForm() restituisce la matrice di trasformazione per
l'oggetto da applicare alla geometria per ottenere la posizione e
l'orientamento corrente dell'oggetto.
Finiamo questo tutorial con due effetti speciali offerti dall'engine di
Genesis e per nulla difficili da utilizzare. Si tratta dell'effetto Corona e
delle luci dinamiche.
Spiegare l'effetto corona viene immensamente meglio se si correda la
spiegazione con una semplice immagine illustrativa:
Questo effetto di luce, tipico dei cartoni animati è molto facile da
realizzare.
Ecco il codice per l'inizializzazione:
void
Corona_Init(geEngine *TheEngine, geWorld *CWorld, geVFile *MainFS) {
CoronaBitmap =
geBitmapUtil_CreateFromFileAndAlphaNames(MainFS, "Bmp\\Corona.Bmp",
"Bmp\\Corona_a.Bmp");
geWorld_AddBitmap(World, CoronaBitmap);
}
Sostanzialmente viene caricata una immagine bitmap chiamata corona
insieme ad un'altra immagine che funge da livello Alpha per la prima.
La funzione per il rendering
Corona_Frame(geWorld
*World, const geXForm3d *XForm, geFloat DeltaTime) {
geEntity_EntitySet *Set;
geEntity * Entity;
GE_Collision Collision;
Set = geWorld_GetEntitySet(World,
"Corona");
if (Set == NULL)
return GE_TRUE;
Entity =
geEntity_EntitySetGetNextEntity(Set, NULL);
while (Entity)
{
Corona * C;
geVec3d Pos;
geFloat DistanceToCorona;
geVec3d Delta;
geFloat Radius;
geBoolean Visible;
geBoolean Fading;
int32 Leaf;
C = geEntity_GetUserData(Entity);
[codice per determinare se il punto è
visibile]
if (Visible || Fading) {
GE_LVertex Vert;
Vert.X
= Pos.X;
Vert.Y
= Pos.Y;
Vert.Z = Pos.Z;
Vert.r = C->Color.r;
Vert.g = C->Color.g;
Vert.b = C->Color.b;
Vert.a = 255.0f;
Vert.u = Vert.v = 0.0f;
if (Visible) {
if (DistanceToCorona >=
C->MaxRadiusDistance)
Radius = (geFloat)C->MaxRadius;
else
if (DistanceToCorona <=
C->MinRadiusDistance)
Radius = (geFloat)C->MinRadius;
else
{
geFloat Slope;
Slope =
(geFloat)(C->MaxRadius - C->MinRadius) /
(geFloat)(C->MaxRadiusDistance - C->MinRadiusDistance);
Radius =
(geFloat)C->MinRadius + Slope * (geFloat)(DistanceToCorona -
C->MinRadiusDistance);
}
C->LastVisibleRadius
= Radius;
} else
Radius = (1.0f -
(C->LastTime - C->LastVisibleTime) / C->FadeTime) *
C->LastVisibleRadius;
geWorld_AddPolyOnce(World,
&Vert,
1,
CoronaBitmap,
GE_TEXTURED_POINT,
GE_RENDER_DO_NOT_OCCLUDE_OTHERS
| GE_RENDER_DO_NOT_OCCLUDE_SELF,
Radius * EffectScale);
C->LastTime
+= DeltaTime;
}
Entity =
geEntity_EntitySetGetNextEntity(Set, Entity);
}
}
Non fa altro che posizionare un poligono con la texture Corona nel
punto desiderato in modo che sia sempre rivolta verso la camera (e quindi non
si noti il fatto che si tratta di una immagine 2D).
Effetto sofisticato che serve a riprodurre una luce di intensità non
costante. La variazione è totalmente programmabile dall'utente. E' ottimale per
creare effetti come torce, luci difettose che tardano ad accendersi e
contribuiscono a creare atmosfera alla scena.
Ecco il codice:
geBoolean DynLight_Frame(geWorld *World,
const geXForm3d *XForm, geFloat DeltaTime)
{
geEntity_EntitySet
*Set;
geEntity
* Entity;
Set
= geWorld_GetEntitySet(World, "DynamicLight");
Entity
= geEntity_EntitySetGetNextEntity(Set, NULL);
while (Entity) {
DynamicLight
*Light;
geFloat Radius;
geFloat Percentage;
int Index;
geVec3d Pos;
Light
= geEntity_GetUserData(Entity);
assert(Light->DynLight);
if (Light->Model) {
geXForm3d XForm;
geWorld_GetModelXForm(World,
Light->Model, &XForm);
Pos
= Light->origin;
if (Light->AllowRotation) {
geVec3d Center;
geWorld_GetModelRotationalCenter(World,
Light->Model, &Center);
geVec3d_Subtract(&Pos, &Center, &Pos);
geXForm3d_Transform(&XForm,
&Pos, &Pos);
geVec3d_Add(&Pos,
&Center, &Pos);
} else
geVec3d_Add(&Pos,
&XForm.Translation, &Pos);
}
else
Pos
= Light->origin;
Percentage
= Light->LastTime / Light->RadiusSpeed;
Index
= (int)(Percentage * Light->NumFunctionValues);
if (Light->InterpolateValues &&
Index < Light->NumFunctionValues - 1) {
geFloat Remainder;
geFloat InterpolationPercentage;
int DeltaValue;
geFloat Value;
Remainder
= (geFloat)fmod(Light->LastTime, Light->IntervalWidth);
InterpolationPercentage
= Remainder / Light->IntervalWidth;
DeltaValue
= Light->RadiusFunction[Index + 1] - Light->RadiusFunction[Index];
Value
= Light->RadiusFunction[Index] + DeltaValue * InterpolationPercentage;
Percentage
= ((geFloat)(Value - 'a')) / ((geFloat)('z' - 'a'));
}
else
Percentage
= ((geFloat)(Light->RadiusFunction[Index] - 'a')) / ((geFloat)('z' - 'a'));
Radius
= Percentage * (Light->MaxRadius - Light->MinRadius) +
Light->MinRadius;
geWorld_SetLightAttributes(World,
Light->DynLight,
&Pos,
&Light->Color,
Radius,
GE_TRUE);
Light->LastTime
= (geFloat)fmod(Light->LastTime + DeltaTime, Light->RadiusSpeed);
Entity
= geEntity_EntitySetGetNextEntity(Set, Entity);
}
return
GE_TRUE;
}
In pratica consiste nel variare gli attributi di una sorgente di luce mediante il comando geWorld_SetLightAttributes.
Questo articolo è stato scaricato
dal Club di informatica |